﻿/*****************************************************
**Copyright(C), 2021-2022, Ciqtek Technology.
**Version:     1.0
**Author:      gaosh
**Date:        2022/08/04
**Description:
               SQLITE 数据库封装库，支持多线程连接数据库，用户无需关心连接的问题
               该类做到一个线程的数据库只存在一个连接，提升性能
               使用引用计数的思想对每个线程的连接单独计数，当计数为0时才关闭连接
               使用读写锁进行线程之间的同步
               默认的数据库文件名为 ciqtek.db
*****************************************************/

#ifndef SQLDATABASE_H
#define SQLDATABASE_H

#include "Base/NameSpace.h"
#include "common_global.h"

#include <QObject>
#include <QHash>
#include <QReadLocker>
#include <QtSql/QSqlDatabase>

#include <atomic>

BEGIN_NAMESPACE_CIQTEK
class COMMON_EXPORT SqlDatabase : public QObject
{
    Q_OBJECT
public:
    /**
     * @brief   SqlDatabase 构造函数
     * @param   parent 父对象
     */
    explicit SqlDatabase(QObject *parent = nullptr);

    /**
     * @brief   ~SqlDatabase 析构函数
     */
    virtual ~SqlDatabase();

    /**
     * @brief   open 打开数据库
     * @param   strDBName 需要打开的数据库名称，为空就使用默认的数据库名称
     * @return  是否打开成功
     * @note    1.如果数据库已打开，但strDBName与当前打开的不一致，则会关闭当前数据库后重新打开
                只有当前线程没有打开过连接时是真正的打开，其余只是内部的引用计数+1
                2.错误时，使用 getLastError 查看错误信息
     */
    bool open(QString strDBName = "");

    /**
     * @brief   close 关闭数据库
     * @param   strDBName 需要关闭的数据库名称，为空就使用当前已打开的数据库
     * @return  是否关闭成功
     * @note   1.关闭某一个数据库的连接，如果未指定则使用当前已打开的连接,这个不是真正的关闭连接，
               只有当内部的引用计数减少为0时才会真正的关闭连接
                2.错误时，使用 getLastError 查看错误信息
     */
    bool close(QString strDBName = "");

    /**
     * @brief   useMultiThread 开启/关闭多线程模式
     * @param   bOpened true，开启多线程模式。false，关闭多线程模式
     * @return
     * @note    请在数据库开始工作前设置，否则可能产生死锁
     */
    static void useMultiThread(bool bOpened);

    /**
     * @brief   isUseMultiThread 查询当前是否开启了多线程模式
     * @param
     * @return  true，开启多线程模式。false，关闭多线程模式
     */
    static bool isUseMultiThread();

    /**
     * @brief   getDatabase 获取对应数据库名称的Database，如果数据库未打开则会先打开数据库
     * @param   strDBName 数据库名称
     * @return  返回对应的Database
     * @note    如果当前线程的连接关闭，则QSqlDatabase就会失效，请勿保存QSqlDatabase在副本中
     */
    QSqlDatabase getDatabase(QString strDBName = "");

    /**
     * @brief   getLastError 获取错误信息
     * @param
     * @return  错误信息
     */
    QString getLastError();

    /**
     * @brief   exec 数据库执行
     * @param   strCmd 数据库执行语句
     * @param   bSuccess 执行是否成功
     * @param   bIsRead
                如果isUseMultiThread为false，第三个参数省略
                如果isUseMultiThread为true，第三个参数会加上锁
                isRead为true，读锁
                isRead为false,写锁
     * @return  返回执行后的QSqlQuery
     */
    QSqlQuery exec(const QString& strCmd, bool &bSuccess, bool bIsRead = true);

    /**
     * @brief       insertQuery     插入操作
     * @param       strTableName    表名
     * @param       strFields       字段
     * @param       strValues       值
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery insertQuery(const QString &strTableName, const QString &strFields, const QString &strValues, bool &bSuccess);

    /**
     * @brief       insertQuery     插入操作（字段省略）
     * @param       strTableName    表名
     * @param       strValues       值
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery insertQuery(const QString &strTableName, const QString &strValues, bool &bSuccess);

    /**
     * @brief       insertQuery     插入或更新操作
     * @param       strTableName    表名
     * @param       strFields       字段
     * @param       strValues       值
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery insertOrReplaceQuery(const QString &strTableName, const QString &strFields, const QString &strValues, bool &bSuccess);

    /**
     * @brief       insertQuery     插入或更新操作（字段省略）
     * @param       strTableName    表名
     * @param       strValues       值
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery insertOrReplaceQuery(const QString &strTableName, const QString &strValues, bool &bSuccess);

    /**
     * @brief       deleteQuery     删除操作
     * @param       strTableName    表名
     * @param       strWheCondition 查找条件
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery deleteQuery(const QString &strTableName, const QString &strWheCondition, bool &bSuccess);

    /**
     * @brief       deleteQuery     删除操作（全表删除）
     * @param       strTableName    表名
     * @param[out]  bSuccess        执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery deleteQuery(const QString &strTableName, bool &bSuccess);

    /**
     * @brief       updateQuery         更新操作
     * @param       strTableName        表名
     * @param       strSetFields        字段
     * @param[out]  bSuccess            执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery updateQuery(const QString &strTableName, const QString &strSetFields, bool &bSuccess);

    /**
     * @brief       updateQuery         更新操作
     * @param       strTableName        表名
     * @param       strSetFields        字段
     * @param       strWheCondition     查找条件
     * @param[out]  bSuccess            执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery updateQuery(const QString &strTableName, const QString &strSetFields, const QString &strWheCondition, bool &bSuccess);

    /**
     * @brief       selectQuery             查询操作
     * @param       strTableName            表名
     * @param[out]  bSuccess                执行是否成功
     * @param       strFields               字段
     * @param       strWheCondition         查找条件
     * @param       strGroupCondition       分组条件
     * @param       strOrderCondition       排序条件
     * @param       nLimNum                 limit起始值
     * @param       nOffNum                 limit数量
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery selectQuery(const QString &strTableName, bool &bSuccess,
        const QString &strFileds = "*",
        const QString &strWheCondition = "", const QString &strGroupCondition = "",
        const QString &strOrderCondition = "", int nLimNum = -1, int nOffNum = -1);

    /**
     * @brief       checkColumn             检查表某一字段是否存在
     * @param       strTableName            表名
     * @param       strColumnName           字段名
     * @param[out]  bSuccess                执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery checkColumnQuery(const QString &strTableName, const QString &strColumnName, bool &bSuccess);

    /**
     * @brief       setColumn               检查表的字段
     * @param       strTableName            表名
     * @param       strColumnName           字段名
     * @param       strProperty             字段属性
     * @param[out]  bSuccess                执行是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery setColumnQuery(const QString &strTableName, const QString &strColumnName,const QString &strProperty,bool &bSuccess);

    /**
     * @brief   lockForRead 读锁上锁  
     * @note 只在自定义查询时使用
     */
    void lockForRead();

    /**
     * @brief   lockForWrite 写锁上锁
     * @note 只在自定义查询时使用
     */
    void lockForWrite();

    /**
     * @brief   unlock 解锁
     * @note 只在自定义查询时使用
     */
    void unlock();

    /**
     * @brief       readQuery   读操作
     * @param       cmd         读操作指令
     * @param[out]  bSuccess    是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery readQuery(const QString& cmd, bool& bSuccess);

    /**
     * @brief       writeQuery  写操作
     * @param       cmd         读操作指令
     * @param[out]  bSuccess    是否成功
     * @return      返回执行后的QSqlQuery
     */
    QSqlQuery writeQuery(const QString& cmd, bool& bSuccess);

public:
    /**
     * @brief  连接信息
     */
    struct ConnectedInfo
    {
        bool isInit = false;                ///< 是否初始化
        int threadFlag;                     ///< 基于线程的连接下标
        QHash<QString, int> dbConnectCount; ///< 记录每个数据库的连接数量，为0时关闭连接
    };

private:
    /**
     * @brief   getConnectName 获取连接名
     * @param   strName 当前的数据库名称
     * @param   nThreadFlag 当前线程标志
     * @return  数据库连接名
     */
    QString getConnectName(const QString& strName, const int& nThreadFlag);

private:
    /** 错误信息
     */
    QString m_strError;

    /** 数据库名称
     */
    QString m_strDBName;

    /** 默认的数据库名称
     */
    const static QString g_defaultDBName;

    /** 用于保证连接名称不会重复的自增字段
     */
    static std::atomic_int g_atomicConnects;

    /** 需要多线程使用时开启
     */
    static std::atomic_bool g_atomicFlag;

    /** 读写锁,保证多线程读写数据库的一致性
     */
    static QReadWriteLock g_lock;
};

END_NAMESPACE_CIQTEK
#endif
